package minimips.isa;

import minimips.computer.Computer;
import minimips.exception.DataHazardException;
import minimips.exception.StopInstructionException;

public abstract class Instruction extends Executable {
	private String instructionToken;
	private int opcode;
	private long memoryAddress;
	
	public Instruction(long memoryAddress, int opcode, String instructionToken) {
		this.memoryAddress = memoryAddress;
		this.opcode = opcode;
		this.instructionToken = instructionToken;
	}
	
	public abstract int getMachineCode();
	
	public String getBinaryString() {
		return Long.toBinaryString(getMachineCode());
	}
	
	public String getHexString() {
		return Long.toHexString(getMachineCode());
	}
	
	public int getOpcode() {
		return this.opcode;
	}
	
	public String getInstructionName() {
		return this.instructionToken;
	}
	
	public long getMemoryAddress() {
		return this.memoryAddress;
	}
	
	/**Override to output the instruction in its original string form.**/
	public abstract String toString();

	@Override
	protected void fetchInstruction(Computer computer) throws StopInstructionException {
		computer.getIf_id_ir().setRegisterValue(this.getMachineCode());
		long oldValue = computer.getIf_id_npc().getRegisterValue();
		if (computer.willJump()) {
			computer.getIf_id_npc().setRegisterValue(computer.getJumpValue());
			computer.resetJumpChecker();
			throw new StopInstructionException();
		}
		else if (computer.willBranch()) {
			computer.getIf_id_npc().setRegisterValue(oldValue + (computer.getJumpValue() << 2));
			computer.resetJumpChecker();
			throw new StopInstructionException();
		}
		else {
			computer.getIf_id_npc().setRegisterValue(oldValue + 4);
		}
	}

	@Override
	protected void decodeInstruction(Computer computer) throws DataHazardException {
		int registerIndexA = (int)((computer.getId_ex_ir().getRegisterValue() >> 21) & 31);
		Long valueForA = getRegisterValue(computer, registerIndexA);
		computer.getId_ex_a().setRegisterValue(valueForA);
		
		int registerIndexB = (int)((computer.getId_ex_ir().getRegisterValue() >> 16) & 31);
		Long valueForB = getRegisterValue(computer, registerIndexB);
		computer.getId_ex_b().setRegisterValue(valueForB);
		
		long valueForImm = computer.getId_ex_imm().getRegisterValue() & 65535;
		computer.getId_ex_imm().setRegisterValue(valueForImm);
	}
	
	private Long getRegisterValue(Computer computer, int registerIndex) throws DataHazardException {
		Long valueForRegister;
		if (computer.isRegisterIndexInUse(registerIndex)) {
			valueForRegister = computer.getValueInCache(registerIndex);
			if (valueForRegister == null) {
				throw new DataHazardException();
			}
		}
		else {
			valueForRegister = computer.getProgrammableRegisterValue(registerIndex);
		}
		
		return valueForRegister;
	}
}
